/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * ns-2 simple.tcl script (ported from ns-2)
 * Originally authored by Steve McCanne, 12/19/1996
 */

// Port of ns-2/tcl/ex/simple.tcl to ns-3
//
// Network topology
//
//  h0                                 h0-n0, h1-n0, n1-h2, n2-h2: 2Mb/s, 10ms
//     \    ---- n1 -----              n0-n1, n0-n2:               2Mb/s, 50ms
//      \  /             |
//       n0              n3 --- h2
//      /  \             |
//     /    ---- n2 ----
//  h1
//
// - all links are point-to-point links with indicated one-way BW/delay
// - FTP/TCP flow from n0 to n3, starting at time 1.2 to time 1.35 sec.
// - DropTail queues 
// - Tracing of queues and packet receptions to file "simple-global-routing.tr"

#include <iostream>
#include <fstream>
#include <string>
#include <cassert>

#include "ns3/core-module.h"
#include "ns3/simulator-module.h"
#include "ns3/node-module.h"
#include "ns3/helper-module.h"
#include "ns3/point-to-point-net-device.h"
//#include "reflex-trace.h"
//#include "texcp-aux.h"
#include "ns3/texcp-queue.h"

using namespace ns3;

using namespace texcp_routing;

NS_LOG_COMPONENT_DEFINE ("SimpleGlobalRoutingExample");

 
typedef std::vector<NetDeviceContainer> NetDevices;
typedef std::vector<NetDeviceContainer>::iterator NetDevicesIterator;

void assign_network( Ipv4AddressHelper *ipv4, NetDevices *netdevices )
{
  NetDevicesIterator iter;
  for (iter=netdevices->begin(); iter != netdevices->end(); iter++) {
    ipv4->Assign (*iter);
    ipv4->NewNetwork();
  }                               
}

void printIPs(NodeContainer nodes)
{
  Ipv4Address loopback("127.0.0.1");

  NodeContainer::Iterator iter;

  for (iter = nodes.Begin(); iter != nodes.End(); iter++) {
    Ptr<Node> node = (*iter);
    Ptr<Ipv4> ipv4 = node->GetObject<Ipv4>();
    NS_ASSERT(ipv4 != 0);
                       
    for (uint32_t index = 0; index < ipv4->GetNInterfaces(); index++) {
      Ipv4InterfaceAddress ipv4Address = ipv4->GetAddress(index,0);

      if (ipv4Address.GetLocal() != loopback) {
        std::cout << " node " << node->GetId()<<":"<< index << " ip " << ipv4Address.GetLocal() << " mask " << ipv4Address.GetMask() << std::endl;
      }
      // end iterating over interfaces
    }
  // end iterate over nodes
  }     
}

void
LogTime ()
{
  NS_LOG_INFO ("Time is " << Simulator::Now());
  Simulator::Schedule(Seconds(5), MakeEvent( &LogTime));
}

int 
main (int argc, char *argv[])
{
  // Users may find it convenient to turn on explicit debugging
  // for selected modules; the below lines suggest how to do this
#if 1
  LogComponentEnable ("SimpleGlobalRoutingExample", LOG_LEVEL_INFO);
  //LogComponentEnable("GlobalRoutingHelper", LOG_LEVEL_ALL);
  LogComponentEnable("Ipv4GlobalRouting", LOG_LEVEL_INFO);
  //LogComponentEnable("Ipv4TexcpRouting", LOG_LEVEL_INFO);
  //LogComponentEnable("TexcpQueue", LOG_LEVEL_ALL);
  //LogComponentEnable("FlowApplication", LOG_LEVEL_ALL);
//  LogComponentEnable("NscTcpSocketImpl", LOG_LEVEL_ALL);
#endif
                                                       
  NS_LOG_INFO ("Create nodes.");
  NodeContainer hosts;
  hosts.Create (6);
  NodeContainer core;
  core.Create (6);
  NodeContainer ingress;
  ingress.Create (3);
  NodeContainer egress;
  egress.Create (3);
 
  //hosts to ingress nodes
  NodeContainer h0i0 = NodeContainer (hosts.Get(0), ingress.Get (0));
  NodeContainer h1i1 = NodeContainer (hosts.Get(1), ingress.Get (1));
  NodeContainer h2i2 = NodeContainer (hosts.Get(2), ingress.Get (2));

  //ingress nodes to core nodes
  NodeContainer i0c0 = NodeContainer (ingress.Get(0), core.Get (0));
  NodeContainer i0c1 = NodeContainer (ingress.Get(0), core.Get (1));
  NodeContainer i1c0 = NodeContainer (ingress.Get(1), core.Get (0));
  NodeContainer i1c2 = NodeContainer (ingress.Get(1), core.Get (2));
  NodeContainer i2c1 = NodeContainer (ingress.Get(2), core.Get (1));
  NodeContainer i2c2 = NodeContainer (ingress.Get(2), core.Get (2));
     
  //internal core connections
  NodeContainer c0c3 = NodeContainer (core.Get(0), core.Get (3));
  NodeContainer c1c4 = NodeContainer (core.Get(1), core.Get (4));
  NodeContainer c2c5 = NodeContainer (core.Get(2), core.Get (5));

  //core to egress
  NodeContainer c3e0 = NodeContainer (core.Get(3), egress.Get (0));
  NodeContainer c3e1 = NodeContainer (core.Get(3), egress.Get (1));
  NodeContainer c4e0 = NodeContainer (core.Get(4), egress.Get (0));
  NodeContainer c4e2 = NodeContainer (core.Get(4), egress.Get (2));
  NodeContainer c5e1 = NodeContainer (core.Get(5), egress.Get (1));
  NodeContainer c5e2 = NodeContainer (core.Get(5), egress.Get (2));

  //egress to hosts
  NodeContainer e0h3 = NodeContainer (egress.Get (0), hosts.Get(3));
  NodeContainer e1h4 = NodeContainer (egress.Get (1), hosts.Get(4));
  NodeContainer e2h5 = NodeContainer (egress.Get (2), hosts.Get(5));

  //install texcp routing in ingress nodes
  InternetStackHelper texcpStack;
  Ipv4TexcpRoutingHelper texcpRoutingHelper;
  Ipv4GlobalRoutingHelper ipv4GlobalRoutingHelper;
  Ipv4ListRoutingHelper listRouting;
  listRouting.Add (texcpRoutingHelper, 1);
  listRouting.Add (ipv4GlobalRoutingHelper, -1);
  texcpStack.SetRoutingHelper (listRouting);    
  texcpStack.Install (ingress);
  texcpStack.Install (egress);

  //install default stack in core and egress
  InternetStackHelper internet;
  internet.Install (core);

  //install NSC stack in hosts
  internet.SetTcp ("ns3::NscTcpL4Protocol","Library",StringValue("liblinux2.6.26.so"));
  internet.Install (hosts);
 
  //activate reflex on hosts.
  /*NodeContainer::Iterator node;
  NodeContainer reflex_hosts = hosts;
  for ( node = reflex_hosts.Begin(); node != reflex_hosts.End(); node++)
  {
      std::ostringstream tcp_ecn;

      tcp_ecn << "/NodeList/" << (*node)->GetId() << "/$ns3::Ns3NscStack<linux2.6.26>/net.ipv4.tcp_ecn";
      Config::Set (tcp_ecn.str(), StringValue("3"));
  }*/
    
  NS_LOG_INFO ("Create channels.");
  PointToPointHelper p2p;

  NS_LOG_INFO ("\thosts to ingress.");
  p2p.SetDeviceAttribute ("DataRate", StringValue ("100Mbps"));
  p2p.SetChannelAttribute ("Delay", StringValue ("10ms"));
  NetDevices nd_h_i;
  nd_h_i.push_back (p2p.Install (h0i0));
  nd_h_i.push_back (p2p.Install (h1i1));
  nd_h_i.push_back (p2p.Install (h2i2));

  //egress to host
  NS_LOG_INFO ("\tegress to host.");
  NetDevices nd_e_h;
  nd_e_h.push_back (p2p.Install (e0h3));
  nd_e_h.push_back (p2p.Install (e1h4));
  nd_e_h.push_back (p2p.Install (e2h5));

  NS_LOG_INFO ("\tingress to core.");
  std::string dataRate = "100Mbps";
  p2p.SetDeviceAttribute ("DataRate", StringValue (dataRate));
  p2p.SetQueue("ns3::texcp_routing::TexcpQueue", "DataRate", StringValue(dataRate));
  p2p.SetChannelAttribute ("Delay", StringValue ("10ms"));
  NetDevices nd_i_c;
  nd_i_c.push_back (p2p.Install (i0c0));
  nd_i_c.push_back (p2p.Install (i0c1));
  nd_i_c.push_back (p2p.Install (i1c0));
  nd_i_c.push_back (p2p.Install (i1c2));
  nd_i_c.push_back (p2p.Install (i2c1));
  nd_i_c.push_back (p2p.Install (i2c2));

  NS_LOG_INFO ("\tcore to egress.");
  NetDevices nd_c_e;
  nd_c_e.push_back (p2p.Install (c3e0));
  nd_c_e.push_back (p2p.Install (c3e1));
  nd_c_e.push_back (p2p.Install (c4e0));
  nd_c_e.push_back (p2p.Install (c4e2));
  nd_c_e.push_back (p2p.Install (c5e1));
  nd_c_e.push_back (p2p.Install (c5e2));

  NS_LOG_INFO ("\tcore to core.");
  NetDevices nd_c_c;
  
  // Configuring link 1
  dataRate = "10Mbps";
  p2p.SetDeviceAttribute ("DataRate", StringValue (dataRate));
  p2p.SetQueue("ns3::texcp_routing::TexcpQueue", "DataRate", StringValue(dataRate));
  nd_c_c.push_back (p2p.Install (c0c3));
  Ptr<PointToPointNetDevice> l1 = DynamicCast<PointToPointNetDevice>((nd_c_c.at(0)).Get(0));
  Ptr<TexcpQueue> queueL1 = DynamicCast<TexcpQueue>(l1->GetQueue());
  queueL1->SetOutput("utils-l1-2.tr");

   // Configuring link 2
  dataRate = "10Mbps";
  p2p.SetDeviceAttribute ("DataRate", StringValue (dataRate));
  p2p.SetQueue("ns3::texcp_routing::TexcpQueue", "DataRate", StringValue(dataRate));
  nd_c_c.push_back (p2p.Install (c1c4));
  Ptr<PointToPointNetDevice> l2 = DynamicCast<PointToPointNetDevice>((nd_c_c.at(1)).Get(0));
  Ptr<TexcpQueue> queueL2 = DynamicCast<TexcpQueue>(l2->GetQueue());
  queueL2->SetOutput("utils-l2-2.tr");

  // Configuring link 3
  dataRate = "10Mbps";
  p2p.SetDeviceAttribute ("DataRate", StringValue (dataRate));
  p2p.SetQueue("ns3::texcp_routing::TexcpQueue", "DataRate", StringValue(dataRate));
  nd_c_c.push_back (p2p.Install (c2c5));
  Ptr<PointToPointNetDevice> l3 = DynamicCast<PointToPointNetDevice>((nd_c_c.at(2)).Get(0));
  Ptr<TexcpQueue> queueL3 = DynamicCast<TexcpQueue>(l3->GetQueue());
  queueL3->SetOutput("utils-l3-2.tr");
 

  //nd_e_p.push_back(nd_e_h);
  
  // Later, we add IP addresses.  
  NS_LOG_INFO ("Assign IP Addresses.");
  Ipv4AddressHelper ipv4;
  Ipv4InterfaceContainer interfaces;

  NetDevicesIterator iter;

  ipv4.SetBase ("172.0.0.0", "255.255.255.0");
  assign_network (&ipv4, &nd_h_i);

  ipv4.SetBase ("10.0.0.0", "255.255.255.0");
  assign_network (&ipv4, &nd_i_c);
  assign_network (&ipv4, &nd_c_c);
  assign_network (&ipv4, &nd_c_e);
  ipv4.SetBase ("192.0.0.0", "255.255.255.0");
  assign_network (&ipv4, &nd_e_h);

  // Create router nodes, initialize routing database and set up the routing
  // tables in the nodes.
  ipv4GlobalRoutingHelper.PopulateRoutingTables ();
 
  // Create texcp routes for each Ingress/Egress pair and destination
  NodeContainer sources = NodeContainer (hosts.Get(0), hosts.Get(1), hosts.Get(2));
  //NodeContainer sources = NodeContainer (hosts.Get(0));
  NodeContainer destinations = NodeContainer (hosts.Get(3), hosts.Get(4), hosts.Get(5));
  //NodeContainer destinations = NodeContainer (hosts.Get(3));
  //NodeContainer destinations = NodeContainer (hosts.Get(3));
  int h_index=0;
  for (NodeContainer::Iterator iter = ingress.Begin(); iter != ingress.End(); iter++) {
    Ptr<Ipv4> ipv4i = (*iter)->GetObject<Ipv4>();
    Ptr<Ipv4> ipv4e = egress.Get(h_index)->GetObject<Ipv4>();
    NS_ASSERT((ipv4i != 0)||(ipv4e != 0));
    //check for right topology
    NS_ASSERT((ipv4i->GetNInterfaces()==4)||(ipv4e->GetNInterfaces()==4));

    Ptr<Ipv4TexcpRouting> texcp_routing_ingress = texcpRoutingHelper.GetTexcpRouting (ipv4i);
    Ptr<Ipv4TexcpRouting> texcp_routing_egress = texcpRoutingHelper.GetTexcpRouting (ipv4e);

    Ipv4Address ingAddr = ipv4i->GetAddress(1,0).GetLocal(); 
    /*Ipv4Address egAddr = ipv4e->GetAddress(3,0).GetLocal();
    struct IEPair s  = {ingAddr, egAddr};
    struct IEPair inverted_s = {egAddr, ingAddr};*/
    //NS_LOG_INFO(s);
    //set to know which path to use (a bit of a hack)
    printIPs(destinations);
    int flag = 0;
    int d_index = 0;
    for (NodeContainer::Iterator dst = destinations.Begin(); dst != destinations.End(); dst++) {
      Ptr<Ipv4> dst_ipv4 = (*dst)->GetObject<Ipv4>();
      NS_ASSERT(dst_ipv4 != 0);
      //only support single address for now, interface 1
      NS_ASSERT(dst_ipv4->GetNInterfaces() == 2);
      Ipv4InterfaceAddress ipv4Address = dst_ipv4->GetAddress(1,0);
      Ipv4Address network = ipv4Address.GetLocal().CombineMask(ipv4Address.GetMask());

      //Define the IE pair
      Ptr<Ipv4> ipv4_egress = egress.Get(d_index)->GetObject<Ipv4>();
      Ipv4Address egAddr = ipv4_egress->GetAddress(3,0).GetLocal(); 
      struct IEPair s = {ingAddr, egAddr};
      if (d_index == h_index) 
        {
          texcp_routing_ingress->AddNetworkRouteTo (network, ipv4Address.GetMask(), 2, s);
          texcp_routing_ingress->AddNetworkRouteTo (network, ipv4Address.GetMask(), 3, s);
        }
      else if (!flag)
        {
          texcp_routing_ingress->AddNetworkRouteTo (network, ipv4Address.GetMask(), 2, s);
          flag = 1;
        }
      else  texcp_routing_ingress->AddNetworkRouteTo (network, ipv4Address.GetMask(), 3, s);
      d_index ++;
      //reflex->TraceConnectWithoutContext("Metric", MakeCallback(&MetricTrace));
    }
    
    d_index =0;
    //Set the routes for the egress routers
    for (NodeContainer::Iterator dst = sources.Begin(); dst != sources.End(); dst++) {
      Ptr<Ipv4> dst_ipv4 = (*dst)->GetObject<Ipv4>();
      NS_ASSERT(dst_ipv4 != 0);
      //only support single address for now, interface 1
      NS_ASSERT(dst_ipv4->GetNInterfaces() == 2);
      Ipv4InterfaceAddress ipv4Address = dst_ipv4->GetAddress(1,0);
      Ipv4Address network = ipv4Address.GetLocal().CombineMask(ipv4Address.GetMask());

      //Define the IE pair
      Ptr<Ipv4> ipv4_egress = egress.Get(h_index)->GetObject<Ipv4>();
      Ipv4Address egAddr = ipv4_egress->GetAddress(3,0).GetLocal(); 
      struct IEPair inverted_s = {egAddr, ingAddr};

      texcp_routing_egress->AddNetworkRouteTo (network, ipv4Address.GetMask(), 1, inverted_s);
      //texcp_routing_egress->AddNetworkRouteTo (network, ipv4Address.GetMask(), 2, inverted_s);
      //reflex->TraceConnectWithoutContext("Metric", MakeCallback(&MetricTrace));
    }
   
   //Launch the routing protocols
   texcp_routing_ingress->DoStart(1);
   texcp_routing_egress->DoStart(0);
   h_index++;

  }

 
  NS_LOG_INFO ("Create Applications.");
  NS_LOG_INFO ("Create sink.");

  uint16_t port = 8080;

  Time endtime = Seconds(100);

  int j = 0;
  for (NodeContainer::Iterator iter = destinations.Begin(); iter != destinations.End(); iter++) {
    Ptr<Ipv4> dst_ipv4 = (*iter)->GetObject<Ipv4>();
    Ipv4InterfaceAddress ipv4Address = dst_ipv4->GetAddress(1,0);
    
    // install sink in each destination
    FlowAppHelper sink (Address (InetSocketAddress (ipv4Address.GetLocal(), port)));
    sink.SetAttribute ("FlowNum", UintegerValue (0));
    sink.SetAttribute ("Local", AddressValue (InetSocketAddress (Ipv4Address::GetAny (), port)));
    ApplicationContainer appsink = sink.Install (*iter);
    appsink.Start (Seconds (0.0));
    appsink.Stop (endtime);


    //install sources
    FlowAppHelper onoff (Address (InetSocketAddress (ipv4Address.GetLocal(), port)));
    onoff.SetAttribute ("FlowNum", UintegerValue (5));
    onoff.SetAttribute ("FlowSize", RandomVariableValue (WeibullVariable (2*100*1000,0.5)));//ConstantVariable (100000)));
    //NodeContainer firstApps = NodeContainer (hosts.Get(0), hosts.Get(1));
    ApplicationContainer appsource = onoff.Install (sources);
    appsource.Start (Seconds (j*10 + 10*j*(j-1)  + 0.2));
    appsource.Stop (endtime);
    /*ApplicationContainer appsource2 = onoff.Install (hosts.Get(2));
    appsource2.Start (Seconds (30));
    appsource2.Stop (endtime);*/
    j++;
}


  //asciiFlow.open("flowlog.tr");
  //asciiMetric.open("metriclog.tr");
  
  NS_LOG_INFO ("Run Simulation.");
  
  texcpRoutingHelper.EnableAscii("texcp-router", ingress);  
  texcpRoutingHelper.TraceQueue("texcp-router", core);  
  LogTime();
  Simulator::Stop (endtime);
  Simulator::Run ();
  NS_LOG_INFO ("Done.");

  Simulator::Destroy ();
  return 0;
}
